package org.fjsei.yewu.bpm;

import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.spring.client.annotation.CustomHeaders;
import io.camunda.zeebe.spring.client.annotation.ZeebeCustomHeaders;
import io.camunda.zeebe.spring.client.annotation.VariablesAsType;
import io.camunda.zeebe.spring.client.annotation.JobWorker;
import io.camunda.zeebe.spring.client.exception.ZeebeBpmnError;
import lombok.extern.slf4j.Slf4j;
import md.cm.flow.ApprovalStm;
import md.specialEqp.Report;
import md.specialEqp.inspect.Procedure_Enum;
import org.fjsei.yewu.filter.FlowStat;
import org.fjsei.yewu.resolver.Comngrql;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

/** BPMN文件上面的节点若没有相应的客户机代码来处理也不报错的。
 * 整个Zeebe流程搞下来：就像拼凑的代码片段，更难于驾驭。
* */
@Slf4j
@Component
public class UniversalWorker extends Comngrql {

    @Autowired
    private UniversalService universalService;

    @Deprecated     //有些代码已经和BPMN文件早就脱钩，应该删除了，代码分离找不到对应关系了！！
    @JobWorker( type = "publish-report", autoComplete = true)
    public void handleReport(@VariablesAsType UniversalProcessVariables variables) throws Exception {
        try {
            log.info("赞成的！publish-report, 结果：{}", variables);
            universalService.goUri(variables.getUri());
        } catch (DuplicateReportException ex) {
            throw new ZeebeBpmnError("duplicateMessage", "Could not post Report, it is a duplicate.");
        }
    }
    /**流程进入这个flowNode时刻系统自动回调执行*/
    @Deprecated
    @JobWorker( type = "send-rejection", autoComplete = true)
    public void sendRejection(@VariablesAsType UniversalProcessVariables variables) throws Exception {
        log.info("拒绝情形！send-rejection, 结果：{}", variables);
        // same thing as above, do data transformation and delegate to real business code / service
    }

    /**审核人超时未审核的，只能回退撤销，后续流程终结
     * 按工作日约定截止日期方式 2019-10-02T08:09:40+08:00 -中国UTC， 相对时限方式P14D 或PT1H30M
     * 直接设置duration = date and time(remainingTime) - date and time(now())) 变换。 没触发now()为NULL会报错
     * 若autoComplete改成false，会5分钟再来一次的：流程也会停留这个节点上。
     * 最后直接上可中断的Timer+duration= date and time(expirationDate) - date and time(creationDate) 流程图多出一个回流环节：来处理再次拖延的情况；
     * 若配置不可中断的Timer Boudary +duration: 发现流程不会自动终结，相当仅仅是超时回调用处，但是流程继续按未超时的情形走下去。+Cycle:R/PT4M设置会定期运行。
     * 不能用Timer Boundary Event(non-interrupting)做，Tasklist会生成多个的审核用户任务。
     * 有局限性：审核延期只能再次拖延，不能够缩短延期时间；没法搞：只能取消一整个的流程实例？审核job节点已经启动了定时器没办法动态缩短时间{不依赖expirationDate:把Duration短间隔+Gateway}，@谨慎延期不能撤回了。
     * 因为不可中断的Event Sub Process还活动着，所以整个流程还算Active状态的。
     * 可中断Boundary Timer = date and time(expirationDate) - date and time(creationDate) #用不可中断定时器就会在UserTask列表存在多实例活动着,全都需要人工完成。
     * 超时触发处理：再次回到审核节点必须creationDate和expirationDate重新设置一遍,新实例定时器依据此设置超时，否则旧的不准确(没有扣除已经发生的定时器超时跨时)。
     * 多人会签：complete condition设置= every x in allsigned  satisfies x=true 实际是子流程的多个实例的立刻完成判定的，并非向上级父流程输出的allsigned[1,2]。若改为= some x in allsigned  satisfies x=true 随意有人签。
     * 若改为= some x in allsigned  satisfies x=false 有某人反对的就该回退。
     * 子流程的Multi_Instance多个实例若都结束了必定导致子流程边界外的输出流往前走的！不管您几个人签字还是拒绝签都会前进flow的。AND网关并不一定要两个AND网关配对使用的，出入线网关性质不同。
     * Non_Start节点若直接拖出两个flow线头的等价于AND网关：会同时激活运行两个流程线路。只要有激活的节点实例那么整个父流程就不会因为其他flow走向END节点而终结的。流程会死循环。
     * 程序代码没有USER_TASK对应的JobWorker处理代码也是不会报错的！只要用TaskList工具手动Complete按钮处理也是有办法调试流程的。
     * 有1个mapping output变量，其它变量也得映射？ 边界超时无法捕获最新的内部子流程的输出变量。
     * 针对Embedded subprocess+并行多实例的+边界定时器的情形：无法获取子流程合并的输出。 而 ZeebeCustomHeaders对应BPMN的Head定义没啥用？不能用变量啊。
     * 多实例并发子流程提前完成= allsigned instance of Any and  (some x in allsigned  satisfies (x instance of Any and  (x=false or x="over")) )这里捕获的allsigned可能是父流程启动是看得导致allsigned=NULL错误;实际针对单一个子实例OutPuts子实例确认变量allsigned[]。
     * Sub_Process并行多实例的Completion Condition里面的变量实际是单一个实例的UserTask提交后的变量取值。
     * 子流程判定提前结束多实例时直接采用result 默认变量 = result=false  # result 也可改字符串类型
     * 切换为绝对时间= date and time(expirationDate) - now() 后, 每次用户确认需要设置新expirationDate时间，还得Output映射输出变量。
     * atCheckOvertime函数并没涉及到的：超时处理节点 expirationDate 就没必要Output输出该变量了它又没变动。 部署多个流程都用到type = "check_overtime"如何区分啊
     * UserTask修改报告后再请求签字:在进入多个子实例之前:需要改新的到期时间expirationDate。这是共享的全局变量啊! 个人签名User确认 result= true
     * */
    @JobWorker( type = "check_overtime", autoComplete = false)
    public void atCheckOvertime(@VariablesAsType UniversalProcessVariables variables, final JobClient client, final ActivatedJob job, @CustomHeaders Map<String, String> headers) throws Exception {
        //先看下是否真的已经超时了，有可能再次拖延的情形。
        Instant expirationDate= Instant.parse(variables.getExpirationDate());
        long pasttime = ChronoUnit.MILLIS.between(Instant.now(), expirationDate);
        if(pasttime<=0) {
            log.info("会签——超时情形，回滚！取消记录待删除：{}", variables);
            variables.setOvertimed(true);
            //用.newFailCommand(job).retries(0).send()无法实现预期延期超时的特性。 BPMN节点启动后就不归底层的zeebee提供的API管理：？除非流程图上流程箭头继续走入某节点，才会让修改的变量对上一步运行的流程节点生效，新的job实例。同一个流程实例有可能两个不同节点的两个实例同时间激活运行！。
            client.newCompleteCommand(job.getKey()).variables(variables).send().join();
        }
        else{       //这里假定 expirationDate已经被手动修改了。 expirationDate creationDate
            //尚未真正的超时的情况，继续等待  overtimed  #同一个流程实例同一节点上竟然可能多个job在激活状态的，BPMN也跑出死循环啊
            //下一次流程图上本节点再次运行时：修改的时间才会真的生效的；因为延期处理生效：该审核job前后有两个实例，Tasklist相应地先后出现两个Usertask实例的。
            log.info("会签——检查发现 超时已经人工做延期了：{}", variables);
            variables.setOvertimed(false);
            //Boudary定时器捆绑到多实例Body场景的:需要把已经签名的人员剔除，只剩下还未签字的人员即可。？不过=ispMens变量变更后，那样传递Output?
            //定时器直接绑定到个体子流程的场景没问题，只是超时后会有新的job而已。
            //Instant instant=Instant.now();
            //variables.setCreationDate(instant.toString());      //修正剩余时间片
            client.newCompleteCommand(job.getKey()).variables(variables).send().join();
        }
        // same thing as above, do data transformation and delegate to real business code / service
    }

    /**报告终结：可以提交给客户看了*/
    @JobWorker( type = "report_finish", autoComplete = true)
    public void handleReportFinish(@VariablesAsType UniversalProcessVariables variables) throws Exception {
        setApprovalStmStatus(variables,Procedure_Enum.END);
        try {
            log.info("真完成报告！Report修改关闭了, 结果：{}", variables);
            universalService.goUri(variables.getUri());
        } catch (DuplicateReportException ex) {
            throw new ZeebeBpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
        }
    }
    @JobWorker(type="report_status_fallback", autoComplete=true)
    public void handleReportFallback(@VariablesAsType UniversalProcessVariables variables) throws Exception {
        setApprovalStmStatus(variables,Procedure_Enum.MAKE);        //当前位于再次进入编制修改报告
        try {
            log.info("报告回退到编制！publish-report, 结果：{}", variables);
            universalService.goUri(variables.getUri());
        } catch (DuplicateReportException ex) {
            throw new ZeebeBpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
        }
    }

    private ApprovalStm setApprovalStmStatus(UniversalProcessVariables variables,Procedure_Enum status){
        FlowStat entFlow = entityOf(variables.getEnt(), Report.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new ZeebeBpmnError("notFind", "没找到流转实体");
        ApprovalStm stm= entFlow.getStm();	  //仅仅是状态和历史操作记录，不是真正的流程引擎服务。
        if(null==stm)   throw new ZeebeBpmnError("notFind", "没状态机");
        stm.setSta(status);        //当前位于进入审核环节
        approvalStms.save(stm);
        return stm;
    }

    /**只能针对报告流转这个BPMN注入的代码片段*/
    @JobWorker(type="report_status_verify", autoComplete=true)
    public void handleReportVerify(@VariablesAsType UniversalProcessVariables variables) throws Exception {
        ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.CHECK);        //当前位于进入审核环节
        try {
            log.info("报告等待审核！publish-Report, 结果：{}", variables);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
            //不一定都得是Report实体类。 申请单都行;
            universalService.goUri(variables.getUri());
        } catch (DuplicateReportException ex) {
            throw new ZeebeBpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
        }
    }
    @JobWorker(type="report_status_approve", autoComplete=true)
    public void handleReportApprove(@VariablesAsType UniversalProcessVariables variables) throws Exception {
        ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
        //这个演示异常抛出：Error Boundary Event来接收error code=DOESNT_WORK的异常。 @JobWorker(type = "fail", forceFetchAllVariables = true) handleFailingJob(, @ZeebeVariable String someResult) {}
        //throw new ZeebeBpmnError("DOESNT_WORK", "This will actually never work :-)");
        try {
            log.info("报告等待审批！publish-report, 结果：{}", variables);
            universalService.goUri(variables.getUri());
        } catch (DuplicateReportException ex) {
            throw new ZeebeBpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
        }
    }
    @JobWorker(type="report_status_apprdeny", autoComplete=true)
    public void handleReportApprdeny(@VariablesAsType UniversalProcessVariables variables, final JobClient client, final ActivatedJob job) throws Exception {
        setApprovalStmStatus(variables,Procedure_Enum.CHECK);
        try {
            log.info("审批超时或否决掉！publish-report, 结果：{}", variables);
            //先看下是否真的已超时了。
            Instant expirationDate= Instant.parse(variables.getExpirationDate());
            long pasttime = ChronoUnit.MILLIS.between(Instant.now(), expirationDate);
            if(pasttime<=0) {
                log.info("审批——超时情形：{}", variables);
                //若是审批超时的，退回到审核人员这里：需要修改超时时间！ 默认3个工作日以内=催促下一关的审批人。 工作日计算用专门一个class*可以外部jar包，该class专有的配置文件1-2年内的日期都配置好or实体类表登记。
                variables.setExpirationDate(expirationDate.plus(30, ChronoUnit.MINUTES).toString());
                variables.setOvertimed(true);
                //用.newFailCommand(job).retries(0).send()无法实现预期延期超时的特性。 BPMN节点启动后就不归底层的zeebee提供的API管理：？除非流程图上流程箭头继续走入某节点，才会让修改的变量对上一步运行的流程节点生效，新的job实例。同一个流程实例有可能两个不同节点的两个实例同时间激活运行！。
                client.newCompleteCommand(job.getKey()).variables(variables).send().join();
            }
            universalService.goUri(variables.getUri());
        } catch (DuplicateReportException ex) {
            throw new ZeebeBpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
        }
    }

/* 允许？直接返回变量 给流程引擎吗：
   @JobWorker(type = "bar", autoComplete = true)
    public Map<String, Object> handleBarJob(final JobClient client, final ActivatedJob job, @ZeebeVariable String a) {
        return Collections.singletonMap("someResult", "42");
    }*/

}

